数据绑定
使用视图模型将你的代码连接到编辑器中的绑定元素。
视图模型
Compose
与其他运行时不同,视图模型在 Compose API 中不作为独立对象存在。它们以 ViewModelSource 密封类的形式表示,该类构成用于创建视图模型实例的构建器模式的前半部分。有关后半部分(创建实例),请参阅视图模型实例。
// 按名称获取源
val vmSource = ViewModelSource.Named("My View Model")
// 获取画板的默认源
val vmSource = ViewModelSource.DefaultForArtboard(artboard)
Legacy
// `view` 类型为 RiveAnimationView
view.setRiveResource(R.raw.my_rive_file)
val file = view.controller.file!!
// 按名称获取引用
val vm = file.getViewModelByName("My View Model")
// 按索引获取引用
for (i in 0 until file.viewModelCount) {
val indexedVM = file.getViewModelByIndex(i)
}
// 获取默认视图模型的引用
val defaultVM = file.defaultViewModelForArtboard(view.controller.activeArtboard!!)
视图模型实例
Compose
请参阅视图模型了解如何获取 ViewModelSource。有了它,你可以使用构建器模式创建 ViewModelInstanceSource(后半部分)。然后可以将该源传递给 rememberViewModelInstance,为组合的生命周期创建并记住实例。
// 来自上一节
val vmSource = ViewModelSource.Named("My View Model")
// 空白实例源
val vmiSourceBlank = ViewModelInstanceSource.Blank(vmSource)
// 或
val vmiSourceBlank = vmSource.blankInstance()
// 默认实例源
val vmiSourceDefault = ViewModelInstanceSource.Default(vmSource)
// 或
val vmiSourceDefault = vmSource.defaultInstance()
// 按名称获取实例源
val vmiSourceNamed = ViewModelInstanceSource.Named(vmSource, "My Instance")
// 或
val vmiSourceNamed = vmSource.namedInstance("My Instance")
// 完成的源现在可以与 Rive 文件一起使用来创建并记住实例
val viewModelInstance = rememberViewModelInstance(riveFile, vmiSourceNamed)
此外,你可以使用 Reference 变体从父实例中引用嵌套的视图模型实例。
val myVMI = rememberViewModelInstance(riveFile, mySource)
val referenceSource = ViewModelInstanceSource.Reference(myVMI, "Path/To/Nested VMI")
val nestedVMI = rememberViewModelInstance(riveFile, referenceSource)
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
// 创建空白实例
val vmiBlank = vm.createBlankInstance()
// 创建默认实例
val vmiDefault = vm.createDefaultInstance()
// 按索引创建
for (i in 0 until vm.instanceCount) {
val vmiIndexed = vm.createInstanceFromIndex(i)
}
// 按名称创建
val vmiNamed = vm.createInstanceFromName("My Instance")
绑定
Compose
请参阅 Compose 数据绑定示例。
当 ViewModelInstance 被传递给 Rive 可组合项时,与状态机的绑定会自动发生。
val vmiSource = ViewModelSource.Named("My View Model").namedInstance("My Instance")
val vmi = rememberViewModelInstance(riveFile, vmiSource)
Rive(
riveFile,
viewModelInstance = vmi
)
Legacy
请参阅 Legacy 数据绑定示例。
view.setRiveResource(
R.raw.my_rive_file,
artboardName = "My Artboard",
)
val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")
// 将实例应用到状态机(推荐)
view.controller.stateMachines.first().viewModelInstance = vmi
// 或者,将实例应用到画板
view.controller.activeArtboard?.viewModelInstance = vmi
自动绑定
Compose
由于可组合项的性质,Compose API 中不存在自动绑定。由于它们是函数,与类相比很难从中获取值。回调需要一个空占位符来在触发前记住值,这比直接提 供实例产生更多开销。
等效做法是创建一个无源的视图模型实例。这将在内部创建默认画板、该画板的默认视图模型以及该视图模型的默认实例。然后可以将其传递给 Rive 可组合项。
val vmi = rememberViewModelInstance(riveFile)
Rive(
riveFile,
viewModelInstance = vmi,
)
Legacy
view.setRiveResource(
R.raw.my_rive_file,
autoBind = true,
)
属性
列出属性
Compose
获取视图模型属性是一个挂起操作,因此需要从协程作用域(如 LaunchedEffect)中调用。
LaunchedEffect(riveFile) {
riveFile.getViewModelProperties("My View Model").forEach { property ->
Log.d("My Tag", "Property Name: ${property.name}, Type: ${property.type}")
}
}
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
// 属性列表
val properties = vm.properties
assertContains(
properties,
ViewModel.Property(ViewModel.PropertyDataType.NUMBER, "My Number Property")
)
读取和写入属性
Compose
写入值
Compose API 没有显式的属性对象。相反,属性值直接在 ViewModelInstance 上使用接受路径的方法来设置。
val vmi = rememberViewModelInstance(...)
vmi.setNumberProperty("Path/To/Property", 10f)
读取值
值通过 Kotlin Flow 读取,当值发生变化时会发出最新值。你可以在 LaunchedEffect 中收集此 flow,或使用 collectAsState() 将其转换为 State(或使用 collectAsStateWithLifecycle() 仅在特定生命周期状态期间收集)。
要一次性获取最新值而不进行观察,可以使用终端操作符 first()。
val vmi = rememberViewModelInstance(...)
// 收集为 State
val numberValue by vmi.numberPropertyFlow("Path/To/Property").collectAsState(initial = 0f)
Text(text = "Number value: $numberValue")
// 或收集
LaunchedEffect(vmi) {
vmi.numberPropertyFlow("Path/To/Property").collect { value ->
Log.d("Rive", "Number value changed: $value")
}
// 或一次性获取
val numberValue = vmi.numberPropertyFlow("Path/To/Property").first()
Log.d("Rive", "Current number value: $numberValue")
}
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")
val numberProperty = vmi.getNumberProperty("My Number Property")
// 获取
val numberValue = numberProperty.value
// 设置
numberProperty.value = 10f
嵌套属性路径
Compose
val parent = rememberViewModelInstance(riveFile, ViewModelSource.Named("Parent VM").namedInstance("Parent"))
// 使用引用
val child = rememberViewModelInstance(riveFile, ViewModelInstanceSource.Reference(parent, "Child"))
val nestedNumber = child.numberPropertyFlow("My Nested Number").collectAsState(0f)
// 或使用路径
val nestedNumber = parent.numberPropertyFlow("Child/My Nested Number").collectAsState(0f)
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")
val nestedNumberByChain = vmi
.getInstanceProperty("My Nested View Model")
.getInstanceProperty("My Second Nested VM")
.getNumberProperty("My Nested Number")
val nestedNumberByPath = vmi
.getNumberProperty("My Nested View Model/My Second Nested VM/My Nested Number")
可观察性
Compose
在使用 Compose API 与 Kotlin Flows 时,可观察性是默认行为。当你收集一个属性的 flow 时,每当该属性的值发生变化,你就会收到更新。
val vmi = rememberViewModelInstance(...)
val numberPropertyFlow = vmi.numberPropertyFlow("My Number Property").collectAsState(0f)
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")
val numberProperty = vmi.getNumberProperty("My Number Property")
// 观察
lifecycleScope.launch {
numberProperty.valueFlow.collect { value ->
Log.i("MyActivity", "Value: $value")
}
}
// 或在 Compose 中收集
val numberValue by numberProperty.valueFlow.collectAsState(0f) // 0 作为等待第一个值时的初始值
图片
Compose
请参阅 Compose 数据绑定图片示例。
要设置图片属性,你需要一个 ImageAsset,可以使用字节数组通过 rememberImage 创建。下面的示例为了方便从原始资源加载到 Result 中,但你应该使用最适合你的应用的模式。
val imageBytes by produceState<Result<ByteArray>>(Result.Loading) {
value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.my_image)
.use { Result.Success(it.readBytes()) }
}
}
// `andThen` 映射 Result,仅在 Success 时调用 lambda,否则传播 Failure 和 Loading。
val image = imageBytes.andThen { bytes ->
rememberImage(riveWorker, bytes)
}
// 或合并为一条语句
val image = produceState<Result<ByteArray>>(Result.Loading) {
value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.my_image)
.use { Result.Success(it.readBytes()) }
}
}.value.andThen { bytes ->
rememberImage(riveWorker, bytes)
}
val vmi = rememberViewModelInstance(riveFile, ViewModelSource.Named("My View Model").defaultInstance())
LaunchedEffect(vmi, image) {
when(image) {
is Result.Failure -> { /* 处理加载图片失败 */ }
is Result.Loading -> { /* 如果需要,处理加载状态 */ }
is Result.Success -> {
// 设置图片属性值
vmi.setImage("Image property", image.value)
}
}
}
如果你想在图片加载完成且两者都成功后才展示 Rive 内容,可以使用 zip 便捷函数将多个 Result 对象组合在一起。
val fileAndImage = riveFile.zip(image)
when (fileAndImage) {
is Result.Failure -> { /* 处理加载文件或图片失败 */ }
is Result.Loading -> { /* 如果需要,处理加载状态 */ }
is Result.Success -> {
val (riveFile, image) = fileAndImage.value
// riveFile 和 image 都已成功加载
// 现在可以展示 Rive 内容并设置图片属性
}
}
有关图片资源的更多信息,请参阅加载资源。
Legacy
// 从 assets 文件夹加载图片。
val imageBytes = context.resources.openRawResource(R.raw.my_image).use { stream ->
stream.readBytes()
}
val vmi = it.stateMachines.first().viewModelInstance!!
// 在视图模型实例中用新图片替换图片属性。
val riveImage = RiveRenderImage.fromEncoded(imageBytes)
vmi.getImageProperty("Image property").set(riveImage)
列表
Compose
请参阅 Compose 数据绑定列表示例。
val mainVMI = rememberViewModelInstance(riveFile)
val newListItem = rememberViewModelInstance(riveFile, ViewModelSource.Named("My Item VM").namedInstance("My List Item"))
LaunchedEffect(mainVMI, newListItem) {
val listProperty = "My List"
// 将新项添加到列表末尾
mainVMI.appendToList(listProperty, newListItem)
// 在索引 0 处插入新项
mainVMI.insertToListAtIndex(listProperty, 0, newListItem)
// 交换索引 0 和 1 处的项
mainVMI.swapListItems(listProperty, 0, 1)
// 移除特定实例
mainVMI.removeFromList(listProperty, newListItem)
// 移除索引 0 处的项
mainVMI.removeFromListAtIndex(listProperty, 0)
}
由于列表的动态特性,你可能需要在协程中创建项,而不是提前使用 rememberViewModelInstance